using Devil;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace GameToolkit.Editor
{
    [CustomEditor(typeof(MaterialInstance)), CanEditMultipleObjects]
    public class MaterialInstanceEditor : UnityEditor.Editor
    {
        static GenericMenu sShaderMenu;
        static bool sHasShaderSelected;
        static string sSelectedShader;
        static GUIContent sLabel = new GUIContent();
        static readonly string[] sVectorComponentNames = { "x", "y", "z", "w" };
        static readonly float[] sVectorComponents = { 0, 0, 0, 0 };

        static void GetShadersMenu()
        {
            EditorUtility.DisplayProgressBar("Getting Shaders", "Processing...", 0.1f);
            sShaderMenu = new GenericMenu();
            var shaders = ShaderUtil.GetAllShaderInfo();
            foreach (var shader in shaders)
            {
                if (shader.hasErrors || shader.name.StartsWith("Hidden/") || shader.name.StartsWith("GUI/"))
                    continue;
                sShaderMenu.AddItem(new GUIContent(shader.name), false, SelectShader, shader.name);
            }
            EditorUtility.ClearProgressBar();
        }

        private static void SelectShader(object shader)
        {
            sHasShaderSelected = true;
            sSelectedShader = (string)shader;
        }

        SerializedProperty m_Shader;
        Shader mShader;
        SerializedProperty m_Properties;
        List<ShaderValue> mShaderValues;

        private void OnEnable()
        {
            if (sLabel == null)
                sLabel = new GUIContent();
            m_Shader = serializedObject.FindProperty("m_ShaderName");
            mShader = string.IsNullOrEmpty(m_Shader.stringValue) ? null : Shader.Find(m_Shader.stringValue);
            m_Properties = serializedObject.FindProperty("m_Properties");
            UpdateShaderValues();
        }

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            var dirty = false;
            if (serializedObject.isEditingMultipleObjects)
            {
                EditorGUILayout.HelpBox("当前正在编辑多个材质实例对象", MessageType.Warning);
            }
            EditorGUI.BeginChangeCheck();
            EditorGUILayout.BeginHorizontal();
            //EditorGUILayout.PropertyField(m_Shader);
            var shader = EditorGUILayout.ObjectField("Shader", mShader, typeof(Shader), false) as Shader;
            var drop = GUILayout.Button("其他", "DropDownButton", GUILayout.Width(60));
            EditorGUILayout.EndHorizontal();
            var changeshader = EditorGUI.EndChangeCheck();
            if (sHasShaderSelected)
            {
                sHasShaderSelected = false;
                shader = Shader.Find(sSelectedShader);
            }
            if (shader != mShader)
            {
                mShader = shader;
                m_Shader.stringValue = shader == null ? "" : shader.name;
                changeshader = true;
            }
            dirty |= changeshader;

            EditorGUI.BeginChangeCheck();
            ShaderPropertiesGUILayout(mShaderValues, m_Properties);
            dirty |= EditorGUI.EndChangeCheck();

            if (dirty)
                serializedObject.ApplyModifiedProperties();

            if (drop)
            {
                if (sShaderMenu == null)
                    GetShadersMenu();
                sShaderMenu.ShowAsContext();
            }

            if (changeshader)
            {
                UpdateShaderValues();
            }

        }

        void UpdateShaderValues()
        {
            if (mShaderValues == null)
                mShaderValues = new List<ShaderValue>();
            else
                mShaderValues.Clear();
            GetShaderProperties(mShader, mShaderValues);
        }

        public static void ShaderPropertiesGUILayout(IList<ShaderValue> propertyDeclares, SerializedProperty propertiesLst, string title = null, System.Action validateCallback = null)
        {
            EditorGUILayout.BeginHorizontal("RL Header");
            GUILayout.Space(15);

            propertiesLst.isExpanded = EditorGUILayout.Foldout(propertiesLst.isExpanded, string.IsNullOrEmpty(title) ? propertiesLst.displayName : title, true);
            EditorGUILayout.EndHorizontal();
            if (propertiesLst.isExpanded)
            {
                var rect = EditorGUILayout.BeginVertical("RL Background", GUILayout.MinHeight(20));
                DropDownShaderProperties(propertyDeclares, propertiesLst, validateCallback);
                EditorGUILayout.EndVertical();

                if (rect.Contains(Event.current.mousePosition) &&
                    (Event.current.type == EventType.DragPerform || Event.current.type == EventType.DragUpdated))
                {
                    var objs = DragAndDrop.objectReferences;
                    Material mat = null;
                    MaterialInstance matInst = null;
                    var len = objs == null ? 0 : objs.Length;
                    for (int i = 0; i < len && (mat == null && matInst == null); i++)
                    {
                        mat = objs[i] as Material;
                        matInst = objs[i] as MaterialInstance;
                    }
                    if (mat != null || matInst != null)
                    {
                        DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
                        if (Event.current.type == EventType.DragPerform)
                        {
                            DragAndDrop.AcceptDrag();
                            if (mat != null)
                                CopyShaderProeprties(propertiesLst, mat, validateCallback);
                            if (matInst != null)
                                CopyShaderProeprties(propertiesLst, matInst, validateCallback);
                        }
                    }
                    Event.current.Use();
                }
            }
        }

        static void CopyShaderProeprties(SerializedProperty propertiesLst, Material mat, System.Action validateCallback)
        {
            var size = propertiesLst.arraySize;
            var dirty = false;
            for (int i = 0; i < size; i++)
            {
                var item = propertiesLst.GetArrayElementAtIndex(i);
                var sname = item.FindPropertyRelative("name");
                var typev = item.FindPropertyRelative("type");
                var value = item.FindPropertyRelative("value");
                var v = value.vector4Value;
                var type = (EShaderPropertyType)typev.intValue;

                if (mat.HasProperty(sname.stringValue))
                {
                    if (type == EShaderPropertyType.Float)
                    {
                        var f = mat.GetFloat(sname.stringValue);
                        if (!Mathf.Approximately(f, v.x))
                        {
                            v.x = f;
                            value.vector4Value = v;
                            dirty = true;
                        }
                    }
                    else
                    {
                        var f = type == EShaderPropertyType.Vector ? mat.GetVector(sname.stringValue) : (Vector4)mat.GetColor(sname.stringValue);
                        if (MathExt.IsDifferent(f, v, 0.0001f))
                        {
                            value.vector4Value = f;
                            dirty = true;
                        }
                    }
                }
            }
            GUI.changed |= dirty;
            if (dirty && validateCallback != null)
            {
                validateCallback();
            }
        }

        static void CopyShaderProeprties(SerializedProperty propertiesLst, MaterialInstance mat, System.Action validateCallback)
        {
            var size = propertiesLst.arraySize;
            var dirty = false;
            for (int i = 0; i < size; i++)
            {
                var item = propertiesLst.GetArrayElementAtIndex(i);
                var sname = item.FindPropertyRelative("name");
                var typev = item.FindPropertyRelative("type");
                var value = item.FindPropertyRelative("value");
                var v = value.vector4Value;
                var type = (EShaderPropertyType)typev.intValue;

                var n = mat.IndexOfProperty(sname.stringValue);
                if (n != -1)
                {

                    if (type == EShaderPropertyType.Float)
                    {
                        var f = mat[n].FloatValue;
                        if (!Mathf.Approximately(f, v.x))
                        {
                            v.x = f;
                            value.vector4Value = v;
                            dirty = true;
                        }
                    }
                    else
                    {
                        var f = mat[n].value;
                        if (MathExt.IsDifferent(f, v, 0.0001f))
                        {
                            value.vector4Value = f;
                            dirty = true;
                        }
                    }
                }
            }
            GUI.changed |= dirty;
            if (dirty && validateCallback != null)
            {
                validateCallback();
            }
        }

        static void DropDownShaderProperties(IList<ShaderValue> propertyDeclares, SerializedProperty propertiesLst, System.Action validateCallback)
        {
            int editIndex = -1;
            string editName = null;
            var lv = EditorGUI.indentLevel;
            EditorGUI.indentLevel = 0;

            var size = propertiesLst.FindPropertyRelative("Array.size");
            if (size.hasMultipleDifferentValues)
            {
                EditorGUILayout.BeginHorizontal();
                GUILayout.Space(16);
                EditorGUILayout.LabelField("······", GUILayout.Width(100));
                EditorGUILayout.EndHorizontal();
            }
            else
            {
                for (int i = 0; i < propertiesLst.arraySize; i++)
                {
                    var item = propertiesLst.GetArrayElementAtIndex(i);
                    var sname = item.FindPropertyRelative("name");
                    var typev = item.FindPropertyRelative("type");
                    var value = item.FindPropertyRelative("value");
                    var type = (EShaderPropertyType)typev.intValue;
                    var rect = EditorGUILayout.BeginHorizontal();
                    GUILayout.Space(35);
                    sLabel.text = sname.hasMultipleDifferentValues || typev.hasMultipleDifferentValues ? "[多种属性]" : sname.stringValue;
                    if (type == EShaderPropertyType.Vector)
                    {
                        var v = EditorGUILayout.Vector4Field(sLabel, value.vector4Value);
                        GUI.changed |= !Mathf.Approximately(Vector4.SqrMagnitude(v - value.vector4Value), 0);
                        value.vector4Value = v;
                    }
                    else if (type == EShaderPropertyType.Float)
                    {
                        var n = FindShaderValueIndex(sname.stringValue, propertyDeclares);
                        var vector = value.vector4Value;
                        float v = vector.x;
                        if (n == -1 || propertyDeclares[n].type != ShaderUtil.ShaderPropertyType.Range)
                        {
                            v = EditorGUILayout.FloatField(sLabel, v);
                        }
                        else
                        {
                            v = EditorGUILayout.Slider(sLabel, v, propertyDeclares[n].defValue.y, propertyDeclares[n].defValue.z);
                        }
                        GUI.changed |= !Mathf.Approximately(v, vector.x);
                        vector.x = v;
                        value.vector4Value = vector;
                    }
                    else
                    {
                        var hdr = type == EShaderPropertyType.HdrColor;
                        Vector4 color = EditorGUILayout.ColorField(sLabel, value.vector4Value, true, true, hdr);
                        GUI.changed |= !Mathf.Approximately(Vector4.SqrMagnitude(color - value.vector4Value), 0);
                        value.vector4Value = color;
                        sLabel.text = "HDR";
                        hdr = GUILayout.Toggle(hdr, sLabel, "OL Toggle", GUILayout.Width(50));
                        var tp = hdr ? EShaderPropertyType.HdrColor : EShaderPropertyType.Color;
                        GUI.changed |= tp != (EShaderPropertyType)typev.intValue;
                        typev.intValue = (int)tp;

                    }
                    EditorGUILayout.EndHorizontal();
                    var del = GUI.Button(new Rect(rect.x, rect.y, 15, 16), DevilEditorUtility.EmptyContent, "OL Minus");
                    var drop = GUI.Button(new Rect(rect.x + 16, rect.y, 15, 16), DevilEditorUtility.EmptyContent, "PopupCurveDropdown");
                    if (drop)
                    {
                        editName = sname.stringValue;
                        editIndex = i;
                    }
                    if (del)
                    {
                        propertiesLst.DeleteArrayElementAtIndex(i);
                        GUI.changed = true;
                    }
                }
            }
            EditorGUILayout.BeginHorizontal();
            GUILayout.Space(16);
            EditorGUI.BeginDisabledGroup(propertiesLst.hasMultipleDifferentValues);
            var newp = GUILayout.Button("新建属性", "DropDownButton", GUILayout.Width(120));
            EditorGUI.EndDisabledGroup();
            EditorGUILayout.EndHorizontal();
            GUILayout.Space(5);
            EditorGUI.indentLevel = lv;
            if ((newp || editIndex != -1) && propertyDeclares != null && propertyDeclares.Count > 0)
            {
                var menu = new GenericMenu();
                for (int i = 0; i < propertyDeclares.Count; i++)
                {
                    GUIContent content;
                    //if (string.IsNullOrEmpty(mShaderValues[i].desc))
                    //    content = new GUIContent(string.Format("{0}/{1}", mShaderValues[i].type, mShaderValues[i].name));
                    //else
                    content = new GUIContent(string.Format("{0} ({1} :{2})", propertyDeclares[i].name, propertyDeclares[i].desc, propertyDeclares[i].type));
                    //content.tooltip = mShaderValues[i].ToString();
                    var n = IndexOfShaderProperty(propertyDeclares[i].name, propertiesLst);
                    if (n == -1 || editIndex != -1)
                        menu.AddItem(content, editName == propertyDeclares[i].name, SetShaderValue,
                            new SetPropertyData(propertiesLst, propertyDeclares[i], editIndex, validateCallback));
                    else
                        menu.AddDisabledItem(content, editName == propertyDeclares[i].name);
                }
                menu.ShowAsContext();
            }
        }

        static void SetShaderValue(object data)
        {
            var v = data as SetPropertyData;
            if (v == null)
                return;
            bool isNew = false;
            if (v.index < 0 || v.index >= v.propertyLst.arraySize)
            {
                isNew = true;
                v.propertyLst.arraySize++;
                v.index = v.propertyLst.arraySize - 1;
            }
            var p = v.propertyLst.GetArrayElementAtIndex(v.index);
            p.FindPropertyRelative("name").stringValue = v.declare.name;
            if (v.declare.type == ShaderUtil.ShaderPropertyType.Color)
                p.FindPropertyRelative("type").intValue = (int)EShaderPropertyType.Color;
            else if (v.declare.type == ShaderUtil.ShaderPropertyType.Vector)
                p.FindPropertyRelative("type").intValue = (int)EShaderPropertyType.Vector;
            else
                p.FindPropertyRelative("type").intValue = (int)EShaderPropertyType.Float;
            if (isNew)
            {
                p.FindPropertyRelative("value").vector4Value = v.declare.defValue;
            }
            v.propertyLst.serializedObject.ApplyModifiedProperties();
            v.validateCallback?.Invoke();
        }

        static int IndexOfShaderProperty(string pname, SerializedProperty propertiesLst)
        {
            for (int i = 0; i < propertiesLst.arraySize; i++)
            {
                if (propertiesLst.GetArrayElementAtIndex(i).FindPropertyRelative("name").stringValue == pname)
                    return i;
            }
            return -1;
        }

        static int FindShaderValueIndex(string declare, IList<ShaderValue> declares)
        {
            if (declares == null)
                return -1;
            for (int i = 0; i < declares.Count; i++)
            {
                if (declares[i].name == declare)
                    return i;
            }
            return -1;
        }

        public static void GetShaderProperties(Shader shader, ICollection<ShaderValue> shaderValues)
        {
            if (shader != null)
            {
                var count = ShaderUtil.GetPropertyCount(shader);
                for (int i = 0; i < count; i++)
                {
                    var type = ShaderUtil.GetPropertyType(shader, i);
                    if (type == ShaderUtil.ShaderPropertyType.TexEnv)
                        continue;
                    ShaderValue v;
                    v.type = type;
                    v.desc = ShaderUtil.GetPropertyDescription(shader, i);
                    if (type == ShaderUtil.ShaderPropertyType.Range)
                    {
                        var min = ShaderUtil.GetRangeLimits(shader, i, 1);
                        var max = ShaderUtil.GetRangeLimits(shader, i, 2);
                        var def = ShaderUtil.GetRangeLimits(shader, i, 0);
                        v.defValue = new Vector4(def, min, max);
                    }
                    else if (type == ShaderUtil.ShaderPropertyType.Color)
                    {
                        v.defValue = Vector4.one;
                    }
                    else //if (type == ShaderUtil.ShaderPropertyType.Color)
                    {
                        v.defValue = Vector4.zero;
                    }
                    v.name = ShaderUtil.GetPropertyName(shader, i);
                    shaderValues.Add(v);
                }
            }
        }

        public struct ShaderValue
        {
            public string name;
            public string desc;
            public ShaderUtil.ShaderPropertyType type;
            public Vector4 defValue;
        }

        class SetPropertyData
        {
            public ShaderValue declare;
            public SerializedProperty propertyLst;
            public System.Action validateCallback;
            public int index;

            public SetPropertyData(SerializedProperty propertyLst, ShaderValue declare, int index, System.Action validateCallback)
            {
                this.propertyLst = propertyLst;
                this.declare = declare;
                this.index = index;
                this.validateCallback = validateCallback;
            }
        }
    }
}